package com.gxtbindings.bindings.client;

import java.util.ArrayList;
import java.util.List;

import com.extjs.gxt.ui.client.binding.Converter;
import com.extjs.gxt.ui.client.binding.FieldBinding;
import com.extjs.gxt.ui.client.data.BeanModelFactory;
import com.extjs.gxt.ui.client.data.BeanModelLookup;
import com.extjs.gxt.ui.client.data.Model;
import com.extjs.gxt.ui.client.data.PropertyChangeEvent;
import com.extjs.gxt.ui.client.util.Util;
import com.extjs.gxt.ui.client.widget.form.Field;

public class BindingGroup implements Bindable {

	private List<FieldBinding> bindings = new ArrayList<FieldBinding>();
	
	public void add(Field<?> field, String property)
	{
		add(field, property, null);
	}
	
	public void add(Field<?> field, String property, Converter converter)
	{
		add(field, property, converter, null);
	}
	
	public void add(Field<?> field, String property, Converter converter, final PropertyChangeListener changeListener)
	{
		FieldBinding f = new FieldBinding(field, property) {

			@Override
			protected void onModelChange(PropertyChangeEvent event) {
				super.onModelChange(event);
				
				if (event.getName().equals(property) && changeListener != null) {
					changeListener.onModelChange(event);
				}
			}
		};
		
		if (converter != null)
		{
			f.setConverter(converter);
		}
		bindings.add(f);
	}
	
	public FieldBinding get(Field<?> field)
	{
		for (FieldBinding binding: bindings)
		{
			if (binding.getField() == field || binding.getField().equals(field))
			{
				return binding;
			}
		}
		return null;
	}
	
	public FieldBinding get(String property)
	{
		for (FieldBinding binding: bindings)
		{
			if (binding.getProperty().equals(property))
			{
				return binding;
			}
		}
		return null;
	}
	
	public void remove(Field<?> field)
	{
		FieldBinding binding = get(field);
		if (binding != null)
		{
			binding.unbind();
			bindings.remove(binding);
		}
	}
	
	public void remove(String property)
	{
		FieldBinding binding = get(property);
		if (binding != null)
		{
			binding.unbind();
			bindings.remove(binding);
		}
	}
	
	public void removeAll()
	{
		unbind();
		bindings.clear();
	}
	
	public void bind(Model model)
	{
		for (FieldBinding binding : bindings)
		{
			binding.bind(model);
		}
		commitChanges();
	}
	
	public void bind(Object bean)
	{
		BeanModelFactory factory = BeanModelLookup.get().getFactory(bean.getClass());
		
		if (factory == null)
		{
			throw new RuntimeException("Factory not found for bean class " + bean.getClass());
		}
		
		bind(factory.createModel(bean));
	}
	
	public boolean isValid() {
		for (FieldBinding binding : bindings)
		{
			if (!binding.getField().isValid())
			{
				return false;
			}
		}
		return true;
	}
	
	public boolean hasChanges()
	{
		for (FieldBinding binding : bindings)
		{
			if (!Util.equalWithNull(binding.getField().getOriginalValue(), binding.getField().getValue()))
			{
				return true;
			}
		}
		
		return false;
	}

	public void clear() {
		for (FieldBinding binding : bindings)
		{
			binding.getField().setOriginalValue(null);
			binding.getField().setValue(null);
		}
	}
	
	@Override
	public void commitChanges() {
		for (FieldBinding binding : bindings)
		{
			binding.getField().setOriginalValue(binding.getField().getValue());
		}
	}

	@Override
	public void rejectChanges() {
		for (FieldBinding binding : bindings)
		{
			// if we were to change the field instead, the field would not fire a change event
			// unless Field.fireChangeEventOnSetValue is true
			// instead of manipulating the field, we can simply update the model instead
			Object val = binding.getField().getOriginalValue();
			if (binding.getConverter() != null)
			{
				val = binding.getConverter().convertFieldValue(val);
			}
			binding.getModel().set(binding.getProperty(), val);
		}
	}

	@Override
	public void unbind() {
		for (FieldBinding binding : bindings)
		{
			binding.unbind();
		}
	}
}
